package com.ccjk.beusoft.thirdlib.plugin2;

import android.content.Context;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;

import java.io.File;
import java.lang.reflect.Array;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.util.HashMap;

import dalvik.system.BaseDexClassLoader;
import dalvik.system.DexFile;

/**
 * Created by CCJK on 2017/12/22.
 * <p>
 * 这是保守的方案，插件没有单独的classloader,而是通过在宿主的classloader 的DexPathList
 * 里面pathList中添加插件的element,从而委托给宿主程序去加载，
 * 缺点： 两个插件如果用了同个库的不同版本，会出错
 */
public class PluginManager2 {

    public static final String KEY_PLUGIN_INTENT = "KEY_PLUGIN_INTENT";
    private Context context;
    private File dexOutDir;
    private HashMap<String, PackageInfo> plugins = new HashMap<>();

    private PluginManager2() {

    }

    private static class Instance {
        private static PluginManager2 instance = new PluginManager2();
    }

    public static PluginManager2 get() {
        return Instance.instance;
    }

    public boolean isPlugin(String packageName) {
        try {
            if (packageName != null) {
                return plugins.keySet().contains(packageName);
            }
        } catch (Exception e) {

        }
        return false;
    }

    public void init(Context context) {
        this.context = context.getApplicationContext();
        dexOutDir = new File(context.getCacheDir(), "dex");
        if (!dexOutDir.exists()) {
            boolean isSuccess = dexOutDir.mkdirs();
            if (!isSuccess) {
                dexOutDir.mkdirs();
            }
        }
    }

    public Context getHostContext() {
        return context;
    }

    public void loadPlugin(String apkPath) throws Exception {
        PackageInfo packageInfo = context.getPackageManager().getPackageArchiveInfo(apkPath, PackageManager.GET_ACTIVITIES);
        if (packageInfo == null) {
            throw new Exception("找不到该插件");
        }
        if (plugins.get(packageInfo.packageName) != null) {
            //已经加载
            return;
        }
        ClassLoader hostClassloader = context.getClassLoader();
        //生成插件的dex文件
        new BaseDexClassLoader(apkPath, dexOutDir, null, hostClassloader);

        addPluginElementToClassloader(hostClassloader, new File(apkPath), dexOutDir.listFiles()[0]);
        plugins.put(packageInfo.packageName, packageInfo);
    }



    /**
     * @param cl         宿主的Classloader
     * @param apkFile    插件的apk文件
     * @param optDexFile 插件的dex具体文件，不是目录
     * @throws Exception
     */
    private void addPluginElementToClassloader(ClassLoader cl, File apkFile, File optDexFile) throws Exception {
        //获取 BaseDexClassLoader : pathList
        Field pathListField = BaseDexClassLoader.class.getDeclaredField("pathList");
        pathListField.setAccessible(true);
        Object pathList = pathListField.get(cl);

        //获取 PathList: Element[] dexElements
        Class cDexPathList = Class.forName("dalvik.system.DexPathList");
        Field dexElementsField = cDexPathList.getDeclaredField("dexElements");
        dexElementsField.setAccessible(true);
        Object[] dexElements = (Object[]) dexElementsField.get(pathList);

        //
        Class elementClass = dexElements.getClass().getComponentType();
        Object[] newDexElements = (Object[]) Array.newInstance(elementClass, dexElements.length + 1);

        //构造插件的element
        Constructor constructor = elementClass.getConstructor(File.class, boolean.class, File.class, DexFile.class);
        Object pluginElement = constructor.newInstance(apkFile, false, apkFile,
                DexFile.loadDex(apkFile.getCanonicalPath(), optDexFile.getAbsolutePath(), 0));

        Object[] temp = new Object[]{pluginElement};
        //将原来的elements复制到新的数组里面
        System.arraycopy(dexElements, 0, newDexElements, 0, dexElements.length);
        //将插件的也复制到新的数组里面
        System.arraycopy(temp, 0, newDexElements, dexElements.length, 1);

        //替换pathlist里面的
        dexElementsField.set(pathList, newDexElements);
    }
}
